package asset

import (
	"base"
	"config/helper"
	"emma/deep"
	"emma/errors"
	"fmt"
	"game_server/logic/asset/api"
	"game_server/logic/asset/handler"
	"game_server/model"
	"proto/base"
)

type assetSystem struct {
	handlers map[base.AssetType]asset_handler.Interface
}

var system = new(assetSystem)

func init() {
	system.handlers = make(map[base.AssetType]asset_handler.Interface)
}

func init() {
	asset_api.Register = system.Register
	asset_api.Get = system.Get
	asset_api.Have = system.Have
	asset_api.Handle = system.Handle
}

func (*assetSystem) Register(handler asset_handler.Interface) {
	if handler == nil {
		panic("handler is nil")
	}

	assetType := handler.AssetType()
	if _, exists := system.handlers[assetType]; exists {
		panic(fmt.Sprintf("handler %v is exists", assetType))
	}

	system.handlers[assetType] = handler
}

func (*assetSystem) Get(player *model.Player, itemId base.AssetItemId, entity interface{}) (result base.AssetItemCount, err error) {
	var assetType base.AssetType
	if assetType, err = config_helper.Asset.Type(itemId); err != nil {
		return
	}

	var handler asset_handler.Interface
	if handler, err = system.getHandler(assetType); err != nil {
		return
	}

	args := new(asset_handler.GetArgs)
	args.ItemId = itemId
	args.Entity = entity
	result, err = handler.GetAsset(player, args)

	return
}

func (*assetSystem) Have(player *model.Player, item *base.AssetItem) (err error) {
	var assetType base.AssetType
	if assetType, err = config_helper.Asset.Type(item.Id); err != nil {
		return
	}

	var handler asset_handler.Interface
	if handler, err = system.getHandler(assetType); err != nil {
		return
	}

	args := new(asset_handler.HaveArgs)
	args.ItemId = item.Id
	args.ItemCount = item.Count
	args.Entity = item.Entity
	err = handler.HaveAsset(player, args)

	return
}

func (*assetSystem) Handle(player *model.Player, addItems []*base.AssetItem, removeItems []*base.AssetItem, reason proto_base.ChangeReason, callback func()) (result *asset_handler.Result, err error) {
	// merge items
	if addItems, err = system.merge(addItems); err != nil {
		return
	}
	if removeItems, err = system.merge(removeItems); err != nil {
		return
	}

	changes := make(asset_handler.Changes)
	handlers := make(map[base.AssetType]asset_handler.Interface)
	defer func() {
		if r := recover(); r != nil {
			code := int(proto_base.ErrorCode_ServerInternalError)
			err = errors.New(code, "%v", r)
		}

		if err == nil {
			system.onChange(player, handlers, reason)
		} else {
			system.rollback(player, handlers, changes)
			result = nil
		}
	}()

	if len(removeItems) > 0 {
		for _, item := range removeItems {
			if err = system.Have(player, item); err != nil {
				return
			}
		}
	}

	if len(removeItems) > 0 {
		args := new(asset_handler.RemoveArgs)
		args.Changes = changes
		args.Reason = reason

		for _, item := range removeItems {
			args.ItemId = item.Id
			args.ItemCount = item.Count
			args.Entity = item.Entity

			var handler asset_handler.Interface
			if handler, err = system.removeAsset(player, args); err != nil {
				return
			}

			handlers[handler.AssetType()] = handler
		}
	}

	if len(addItems) > 0 {
		args := new(asset_handler.AddArgs)
		args.Changes = changes
		args.Reason = reason

		for _, item := range addItems {
			args.ItemId = item.Id
			args.ItemCount = item.Count
			args.Entity = item.Entity

			var handler asset_handler.Interface
			if handler, err = system.addAsset(player, args); err != nil {
				return
			}

			handlers[handler.AssetType()] = handler
		}
	}

	result = new(asset_handler.Result)
	result.AddItems = addItems
	result.RemoveItems = removeItems

	args := new(asset_handler.ChangeArgs)
	args.Reason = reason

	if callback != nil {
		callback()
	}

	return
}

func (*assetSystem) removeAsset(player *model.Player, args *asset_handler.RemoveArgs) (handler asset_handler.Interface, err error) {
	var assetType base.AssetType
	if assetType, err = config_helper.Asset.Type(args.ItemId); err != nil {
		return
	}

	if handler, err = system.getHandler(assetType); err != nil {
		return
	}

	if err = handler.RemoveAsset(player, args); err != nil {
		return
	}

	return
}

func (*assetSystem) addAsset(player *model.Player, args *asset_handler.AddArgs) (handler asset_handler.Interface, err error) {
	var assetType base.AssetType
	if assetType, err = config_helper.Asset.Type(args.ItemId); err != nil {
		return
	}

	if handler, err = system.getHandler(assetType); err != nil {
		return
	}

	if err = handler.AddAsset(player, args); err != nil {
		return
	}

	return
}

func (*assetSystem) getHandler(assetType base.AssetType) (handler asset_handler.Interface, err error) {
	var exists bool
	handler, exists = system.handlers[assetType]
	if !exists {
		code := int(proto_base.ErrorCode_UnregisterAssetType)
		err = errors.New(code, "unregister asset type, %v", assetType)
		return
	}

	return
}

func (*assetSystem) merge(items []*base.AssetItem) (results []*base.AssetItem, err error) {
	if len(items) < 1 {
		return
	}

	appendToTail := func(indexes map[int]*base.AssetItem, item *base.AssetItem) {
		result := deep.Clone(item).(*base.AssetItem)
		indexes[len(results)] = result
		results = append(results, result)
	}

	indexMapper := make(map[base.AssetItemId]map[int]*base.AssetItem)
	for _, item := range items {
		if item.Count < 1 {
			code := int(proto_base.ErrorCode_AssetCountIsZero)
			err = errors.New(code, "asset count is zero")
			return
		}

		itemId := item.Id
		indexes, exists := indexMapper[itemId]
		if !exists {
			indexes = make(map[int]*base.AssetItem)
			indexMapper[itemId] = indexes
		}

		if len(indexes) < 1 {
			appendToTail(indexes, item)
		} else {
			var isEqual bool
			for _, result := range indexes {
				isEqual = result.Equal(item)
				if isEqual {
					result.Count += item.Count
					break
				}
			}

			if !isEqual {
				appendToTail(indexes, item)
			}
		}
	}

	return
}

func (*assetSystem) rollback(player *model.Player, handlers map[base.AssetType]asset_handler.Interface, changes asset_handler.Changes) {
	args := new(asset_handler.RollbackArgs)
	args.Changes = changes
	rollback := func(handler asset_handler.Interface) {
		defer func() {
			if r := recover(); r != nil {
				base.Logger.Error("handler: %v rollback panic, %v", handler.AssetType(), r)
			}
		}()

		handler.RollbackAsset(player, args)
	}

	for _, handler := range handlers {
		rollback(handler)
	}
}

func (*assetSystem) onChange(player *model.Player, handlers map[base.AssetType]asset_handler.Interface, reason proto_base.ChangeReason) {
	args := new(asset_handler.ChangeArgs)
	args.Reason = reason

	onChange := func(handler asset_handler.Interface) {
		defer func() {
			if r := recover(); r != nil {
				base.Logger.Error("handler: %v on change panic, %v", handler.AssetType(), r)
			}
		}()

		handler.OnAssetChanged(player, args)
	}

	for _, handler := range handlers {
		onChange(handler)
	}
}
